Allowing no scopes to be provided - #2777
Conversation
wmathurin
commented
Oct 2, 2025
- Checking the scope in the token endpoint response to make sure we got refresh_token
- Refactored onAuthFlowComplete() to make it testable and added tests for it
Checking the scope in the token endpoint response to make sure we got refresh_token (and id)
Throwing an error if it isn't. Refactored onAuthFlowComplete() to make it testable and added tests for it.
Generated by 🚫 Danger |
| } | ||
|
|
||
| @VisibleForTesting | ||
| internal suspend fun onAuthFlowComplete( |
There was a problem hiding this comment.
This is like the original onAuthFlowComplete except with pieces refactored out in private helper methods - which are not called directly but are called through lambda parameters.
| .clientId(consumerKey) | ||
| .nativeLogin(nativeLogin) | ||
| .build() | ||
| account.downloadProfilePhoto() |
There was a problem hiding this comment.
Moved this one into fun addAccount(account: UserAccount?, context: Context, isTestRun: Boolean, loginServerManager: LoginServerManager)
| val scopeParser = ScopeParser(tokenResponse.scope) | ||
|
|
||
| // Check that it has the refresh token scope, otherwise call onAuthFlowError | ||
| if (!scopeParser.hasRefreshTokenScope()) { |
There was a problem hiding this comment.
This check is new to this PR.
There was a problem hiding this comment.
Is not having the refresh token scope actually an invalid state/error? Do you still get the refresh scope if the policy is set to expire immediately?
There was a problem hiding this comment.
We never allowed it. We would add the refresh_token scope during login if it was not in bootconfig. So login would fail if the connected app did not have it configured.
I need to try and see what happens if the connected app does not have the scope and I don't check it and also what happens if it is set to expire immediately.
There was a problem hiding this comment.
@brandonpage
I just tried the following:
- an ECA that does not have refresh_token and a client app that does not specify scopes in bootconfig (and without the check above) => login works / token end point response does not include a refresh token
- an ECA with refresh token set to expire immediately => login works / token end point response includes a refresh token (but it will fail to refresh the access token when used)
I'm okay removing the check. We should include those scenarios in our test plan. Especially to make sure that having that null refresh token does not lead to some unwanted behaviors.
| } | ||
|
|
||
| @Test | ||
| fun testOnAuthFlowComplete_blockIntegrationUser_shouldCallError() = runTest { |
There was a problem hiding this comment.
Tests against the refactored onAuthFlowComplete().
| * @return Scope parameter. | ||
| * @return Scope parameter string (possibly empty). | ||
| */ | ||
| public static String computeScopeParameter(String[] scopes) { |
There was a problem hiding this comment.
This is an important change for this PR.
ScopeParser.computeScopeParameter(scopes) will leave an empty scopes list unchanged.
| * @return Scope parameter string (possibly empty). | ||
| */ | ||
| @JvmStatic | ||
| fun computeScopeParameter(scopes: Array<String>?): String { |
There was a problem hiding this comment.
This is the critical change of this PR.
brandonpage
left a comment
There was a problem hiding this comment.
Looks good! Really appreciate all of the tests and the effort put into making onAuthFlowComplete more testable!
| } | ||
| } | ||
| return false; | ||
| return new ScopeParser(scope).hasScope(scopeToCheck); |
There was a problem hiding this comment.
The syntax isn't as clean in Java, but I wonder if ScopeParser could just be a couple String[] extensions?
Edit: There are several functions in ScopeParser so I see its need to be a class.
| onAuthFlowComplete( | ||
| tokenResponse, | ||
| loginServer, | ||
| consumerKey, | ||
| onAuthFlowError, | ||
| onAuthFlowSuccess, | ||
| buildAccountName, | ||
| nativeLogin, | ||
| SalesforceSDKManager.getInstance().appContext, | ||
| SalesforceSDKManager.getInstance().userAccountManager, | ||
| blockIntegrationUser, | ||
| getRuntimeConfig(SalesforceSDKManager.getInstance().appContext), | ||
| ::updateLoggingPrefsHelper, | ||
| ::fetchUserIdentity, | ||
| ::startMainActivityHelper, | ||
| ::setAdministratorPreferences, | ||
| ::addAccountHelper, | ||
| ::handleScreenLockPolicy, | ||
| ::handleBiometricAuthPolicy, | ||
| ::handleDuplicateUserAccount | ||
| ) |
There was a problem hiding this comment.
Since the original onAuthFlowComplete is internal you could just update the original and add all of the new values with these as the default parameters.
| val scopeParser = ScopeParser(tokenResponse.scope) | ||
|
|
||
| // Check that it has the refresh token scope, otherwise call onAuthFlowError | ||
| if (!scopeParser.hasRefreshTokenScope()) { |
There was a problem hiding this comment.
Is not having the refresh token scope actually an invalid state/error? Do you still get the refresh scope if the policy is set to expire immediately?
| SalesforceSDKManager.getInstance().appContext.startActivity(Intent(SalesforceSDKManager.getInstance().appContext, SalesforceSDKManager.getInstance().mainActivityClass).apply { | ||
| setPackage(SalesforceSDKManager.getInstance().appContext.packageName) | ||
| flags = FLAG_ACTIVITY_NEW_TASK | ||
| }) |
There was a problem hiding this comment.
NIT: Why remove the with(SalesforceSDKManager.getInstance()) block?
There was a problem hiding this comment.
I can put it back - it was more readable.
| const val REFRESH_TOKEN = "refresh_token" | ||
| const val ID = "id" | ||
| private const val SINGLE_SPACE = " " | ||
| private const val EMPTY_STRING = "" |
There was a problem hiding this comment.
I don't dislike this, but I think we typically just use "" in code like how 0 isn't really a magic number. 🤷
There was a problem hiding this comment.
Agreed, I will get rid of it.
A quick test indicated that: - login with a CA/ECA that does not have refresh scope works but the token end point does not contain a refresh token - login with a CA/ECA that has a refresh token that expires immediately works also More testing is needed to make sure that expired or revoked access token is gracefully handled when we don't have a refresh token (and also when we have an expired refresh token).
|
The only test failure "LoginActivityTest.viewModelIsUsingFrontDoorBridge_DefaultValue_onCreateWithoutQrCodeLoginIntent" is unrelated. |